package @modelPackage@;

import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.Criteria;
import org.hibernate.Transaction;
import org.hibernate.criterion.Example;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Property;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.List;

/**
 * Abstract hibernate DAO class
 * @param <T> entity data type (identifier for persistent class)
 * @param <ID> id property data type
 * @author Michal Rost
 */
public abstract class HibernateDAO<T, ID extends Serializable> implements BaseDAO<T, ID> {

  protected Class<T> persistentClass;
  protected boolean sessionShared;

  /**
   * Constructor for abstract hibernate DAO object
   */
  @SuppressWarnings("unchecked")
  public HibernateDAO() {
    this(false);
  }

  /**
   * Constructor for abstract hibernate DAO object
   * @param sessionShared true if session is shared (current session will be used)
   */
  @SuppressWarnings("unchecked")
  public HibernateDAO(boolean sessionShared) {
    persistentClass = (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
    this.sessionShared = sessionShared;
  }

  /**
   * Returns hibernate session instance
   * @return session hibernate session
   */
  protected Session giveSession() {

    // If session is shared, retrieve it
    if (sessionShared) {
      return SessionFactoryManager.getFactory().getCurrentSession();
    }

    // Prepare new session and transaction
    Session session = SessionFactoryManager.getFactory().openSession();
    session.beginTransaction();
    return session;
  }

  /**
   * Recycles session
   * @param session hibernate session
   */
  protected void returnSession(Session session) {

    // If session is shared, do nothing (it will be closed at another place)
    if (sessionShared) {
      return;
    }

    // Commit transaction and close session
    Transaction t = session.getTransaction();
    try {
      t.commit();
    } catch (HibernateException e) {
      t.rollback();
      e.printStackTrace();
    } finally {
      session.close();
    }
  }

  /**
   * Returns persistent class (T class)
   * @return persistent class
   */
  public Class<T> getPersistentClass() {
    return persistentClass;
  }

  /**
   * Finds entity with specifed ID
   * @param id identification of entity
   * @param lock lockmode
   * @return entity instance
   */
  @SuppressWarnings("unchecked")
  public T findById(ID id, boolean lock) {
    Session session = giveSession();
    T result = (T) SessionUtils.findById(session, getPersistentClass(), id, lock);
    returnSession(session);
    return result;
  }

  /**
   * Finds all entity instances
   * @return list of entity instances
   */
  @SuppressWarnings("unchecked")
  public List<T> findAll() {
    return findByCriteria();
  }

  /**
   * Finds entity instances by given criteria
   * @param criterions hibernate criterions
   * @return list of entity instances
   */
  @SuppressWarnings("unchecked")
  public List<T> findByCriteria(Criterion... criterions) {
    Session session = giveSession();
    List<T> result = createCriteria(session, criterions).list();
    returnSession(session);
    return result;
  }

  /**
   * Finds entity instances by given criteria
   * @param firstResult index of first row in result
   * @param maxResults max number of rows
   * @param orderBy name of "order by" column
   * @param asc order direction (true for ascending, false for descending)
   * @param criterions hibernate criterions
   * @return list of entity instances
   */
  @SuppressWarnings("unchecked")
  public List<T> findByCriteria(int firstResult, int maxResults, String orderBy, boolean asc, Criterion... criterions) {
    Session session = giveSession();
    Criteria crit = createCriteria(session, criterions);
    crit.setFirstResult(firstResult);
    crit.setMaxResults(maxResults);
    if (orderBy != null && !"".equals(orderBy)) {
      if (asc) {
        crit.addOrder(Property.forName(orderBy).asc());
      } else {
        crit.addOrder(Property.forName(orderBy).desc());
      }
    }
    List<T> result = crit.list();
    returnSession(session);
    return result;
  }

  /**
   * Finds number of rows that meeting given criteria
   * @param criterions hibernate criterions
   * @return number of rows
   */
  public int findCountByCriteria(Criterion... criterions) {
    Session session = giveSession();
    Criteria crit = createCriteria(session, criterions);
    crit.setProjection(Projections.rowCount());
    int result = (Integer) crit.list().get(0);
    returnSession(session);
    return result;
  }

  /**
   * Finds entity instances with same property values
   * @param example entity instance which contains example property values
   * @param excludeProperties list of property names that are to be excluded
   * @return list of entity instances
   */
  @SuppressWarnings("unchecked")
  public List<T> findByExample(T example, String[] excludeProperties) {
    Session session = giveSession();
    List<T> result = createCriteria(session, example, excludeProperties).list();
    returnSession(session);
    return result;
  }

  /**
   * Finds entity instances with same property values
   * @param example entity instance which contains example property values
   * @return list of entity instances
   */
  @SuppressWarnings("unchecked")
  public List<T> findByExample(T example) {
    return findByExample(example, null);
  }

  /**
   * Connects given entity with hibernate session
   * @param entity entity instance
   * @return entity entity instance
   */
  @SuppressWarnings("unchecked")
  public T makePersistent(T entity) {
    Session session = giveSession();
    session.saveOrUpdate(entity);
    returnSession(session);
    return entity;
  }

  /**
   * Deletes given entity instance from hibernate session
   * @param entity entinty instance
   */
  public void makeTransient(T entity) {
    Session session = giveSession();
    session.delete(entity);
    returnSession(session);
  }

  /**
   * Create criteria querry from given criterions
   * @param criterions hibernate criterions
   * @return criteria
   */
  protected Criteria createCriteria(Session session, Criterion... criterions) {
    Criteria crit = session.createCriteria(getPersistentClass());
    for (Criterion c : criterions) {
      crit.add(c);
    }
    return crit;
  }

  /**
   * Create criteria querry for given example
   * @param example entity instance which contains example property values
   * @param excludeProperties list of property names that are to be excluded
   * @return criteria
   */
  protected Criteria createCriteria(Session session, T example, String[] excludeProperties) {
    Criteria crit = session.createCriteria(getPersistentClass());
    Example sample = Example.create(example);
    if (excludeProperties != null) {
      for (String exclude : excludeProperties) {
        sample.excludeProperty(exclude);
      }
    }
    return crit.add(sample);
  }
}